<!DOCTYPE html>
<!--
Copyright 2017 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->

<link rel="import" href="/tracing/base/math/range.html">
<link rel="import" href="/tracing/extras/chrome/event_finder_utils.html">
<link rel="import" href="/tracing/metrics/metric_registry.html">
<link rel="import" href="/tracing/metrics/spa_navigation_helper.html">
<link rel="import" href="/tracing/metrics/system_health/breakdown_tree_helpers.html">
<link rel="import" href="/tracing/metrics/system_health/loading_metric.html">
<link rel="import" href="/tracing/value/histogram.html">

<script>
'use strict';

tr.exportTo('tr.metrics', function() {
  const SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION_BIN_BOUNDARY =
      tr.v.HistogramBinBoundaries.createExponential(1, 1000, 50);

  /**
   * This metric measures the duration between the input event
   * causing a SPA navigation and the first paint event after it.
   */
  function spaNavigationMetric(histograms, model) {
    const histogram = new tr.v.Histogram(
        'spaNavigationStartToFpDuration',
        tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
        SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION_BIN_BOUNDARY);
    histogram.description = 'Latency between the input event causing' +
        ' a SPA navigation and the first paint event after it';
    histogram.customizeSummaryOptions({
      count: false,
      sum: false,
    });

    const modelHelper = model.getOrCreateHelper(
        tr.model.helpers.ChromeModelHelper);
    if (!modelHelper) {
      // Chrome isn't present.
      return;
    }
    const rendererHelpers = modelHelper.rendererHelpers;
    if (!rendererHelpers) {
      // We couldn't find any renderer processes.
      return;
    }
    const browserHelper = modelHelper.browserHelper;
    for (const rendererHelper of Object.values(rendererHelpers)) {
      const spaNavigations = tr.metrics.findSpaNavigationsOnRenderer(
          rendererHelper, browserHelper);
      for (const spaNav of spaNavigations) {
        let beginTs = 0;
        if (spaNav.navStartCandidates.inputLatencyAsyncSlice) {
          const beginData =
              spaNav.navStartCandidates.inputLatencyAsyncSlice.args.data;
          // TODO(sunjian): rename convertTimestampToModelTime to something like
          // convertTraceEventTsToModelTs and get rid of the first parameter.
          beginTs = model.convertTimestampToModelTime(
              'traceEventClock',
              beginData.INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT.time);
        } else {
          beginTs = spaNav.navStartCandidates.goToIndexSlice.start;
        }
        const rangeOfInterest = tr.b.math.Range.fromExplicitRange(
            beginTs, spaNav.firstPaintEvent.start);
        const networkEvents =
            tr.e.chrome.EventFinderUtils.getNetworkEventsInRange(
                rendererHelper.process, rangeOfInterest);
        const breakdownDict = tr.metrics.sh.generateWallClockTimeBreakdownTree(
            rendererHelper.mainThread, networkEvents, rangeOfInterest);
        const breakdownDiagnostic = new tr.v.d.Breakdown();
        breakdownDiagnostic.colorScheme =
            tr.v.d.COLOR_SCHEME_CHROME_USER_FRIENDLY_CATEGORY_DRIVER;
        for (const label in breakdownDict) {
          breakdownDiagnostic.set(label,
              parseInt(breakdownDict[label].total * 1e3) / 1e3);
        }
        histogram.addSample(
            rangeOfInterest.duration,
            {
              'Breakdown of [navStart, firstPaint]': breakdownDiagnostic,
              'Start': new tr.v.d.RelatedEventSet(spaNav.navigationStart),
              'End': new tr.v.d.RelatedEventSet(spaNav.firstPaintEvent),
              'Navigation infos': new tr.v.d.GenericSet([{
                url: spaNav.url,
                pid: rendererHelper.pid,
                navStart: beginTs,
                firstPaint: spaNav.firstPaintEvent.start
              }]),
            });
      }
    }
    histograms.addHistogram(histogram);
  }

  tr.metrics.MetricRegistry.register(spaNavigationMetric);

  return {
    spaNavigationMetric,
  };
});
</script>
